#include <json.hpp>
#include <iostream>
#include <string>

json::Object getObject() {
  auto obj = json::object();
  obj->setNew("name", json::string("dzlua"));
  obj->setNew("age", json::integer(18));
  obj->setNew("high", json::real(1.76));
  return std::move(obj);
}
json::Array getArray() {
  auto array = json::array();
  for (int i = 0; i < 10; ++i)
    array->appendNew(json::integer(i));
  return std::move(array);
}

void dump(const char *key, json::Json &j, int indent) {
  for (int i = 0; i < indent; ++i)
    std::cout << "\t";
  std::cout << key << ": ";
  switch (j->type()) {
    case json::Type::JSON_OBJECT: {
      auto obj = j->trans<json::Object>();
      if (!obj || !obj->isOK()) break;
      std::cout << std::endl;
      obj->foreach([indent](const char *key, json::Json &v) -> bool {
        dump(key, v, indent+1);
        return true; // true for continue, false for break;
      });
    } break;
    case json::Type::JSON_ARRAY: {
      auto obj = j->trans<json::Array>();
      if (!obj || !obj->isOK()) break;
      std::cout << std::endl;
      obj->foreach([indent](size_t index, json::Json &v) -> bool {
        dump(std::to_string(index).c_str(), v, indent+1);
        return true; // true for continue, false for break;
      });
    } break;
    case json::Type::JSON_STRING: {
      auto obj = j->trans<json::String>();
      if (!obj || !obj->isOK()) break;
      std::cout << obj->value() << std::endl;
    } break;
    case json::Type::JSON_INTEGER: {
      auto obj = j->trans<json::Integer>();
      if (!obj || !obj->isOK()) break;
      std::cout << obj->value() << std::endl;
    } break;
    case json::Type::JSON_REAL: {
      auto obj = j->trans<json::Real>();
      if (!obj || !obj->isOK()) break;
      std::cout << obj->value() << std::endl;
    } break;
    case json::Type::JSON_TRUE:
    case json::Type::JSON_FALSE: {
      auto obj = j->trans<json::Boolean>();
      if (!obj || !obj->isOK()) break;
      std::cout << (obj->value() ? "true" : "false") << std::endl;
    } break;
    case json::Type::JSON_NULL: {
      std::cout << "null" << std::endl;
    } break;
  }
}
void dump(json::Object &obj) {
  obj->incref();
  obj->foreach([](const char *key, json::Json &v) -> bool {
    dump(key, v, 0);
    return true; // true for continue, false for break;
  });
  obj->decref();
}

void testDump() {
  auto obj = json::object();
  obj->setNew("string", json::string("string"));
  obj->setNew("integer", json::integer(100));
  obj->setNew("real", json::real(0.418));
  obj->setNew("number", json::number(0.123));
  obj->setNew("true", json::boolean(true));
  obj->setNew("false", json::boolean(false));
  obj->setNew("null", json::null());
  obj->setNew("object", getObject());
  obj->setNew("array", getArray());
  auto sz = obj->dumps(JSON_INDENT(2));
  std::cout << sz << std::endl;
  json::dumpsFree(sz);
  std::cout << "------------" << std::endl;
  dump(obj);
  obj->decref();
}

json::Object testPack() {
  json::int_t I = 200;
  auto I1 = json::integer(300);
  auto I2 = json::integer(400);
  if (!I1 || !I2) return json::object();
  auto obj = json::pack<json::Object>(
    "{s:s,s:i,s:f,s:f,s:b,s:b,s:n,s:{s:I}:s:[i,i,o,O]}",
    "string", "string",
    "integer", 100,
    "real", 0.418,
    "number", 0.123,
    "true", true,
    "false", false,
    "null",
    "object",
      "integer", I,
    "array",
      0,1,I1.get(),I2.get());
  I2->decref();
  dump(obj);
  return std::move(obj);
}

void testUnpack(json::Object &obj) {
  if (!obj || !obj->isOK()) return;

  const char *string, *notexist = "notexist";
  int integer;
  double real;
  double number;
  json::bool_t true1; 
  json::bool_t false1 = true;
  json::int_t I;
  json::Array array;
  int i0, i1;
  if (obj->unpack(
      "{s:s,s:i,s:f,s:F,s:b,s?b,s:n,s:{s:I}:s:o,s?s}",
      "string", &string,
      "integer", &integer,
      "real", &real,
      "number", &number,
      "true", &true1,
      "false", &false1,
      "null",
      "object", "integer", &I,
      "array", &array.set()),
      "notexist", &notexist) {
    std::cout << "string: " << string << std::endl;
    std::cout << "integer: " << integer << std::endl;
    std::cout << "real: " << real << std::endl;
    std::cout << "number: " << number << std::endl;
    std::cout << "true: " << true1 << std::endl;
    std::cout << "false: " << false1 << std::endl;
    std::cout << "null: " << "null" << std::endl;
    std::cout << "object->integer: " << I << std::endl;
    std::cout << "notexist: " << notexist << std::endl;

    std::cout << "array: ";
    int i0, i1;
    json::Integer I1, I2;
    if (array->unpack("[i,i,o,O]",
        &i0, &i1, &I1.set(), &I2.set())) {
      std::cout << "[";
      std::cout << i0 << ",";
      std::cout << i1 << ",";
      std::cout << I1->value() << ",";
      std::cout << I2->value();
      std::cout << "]" << std::endl;

      I2->decref();
    }
  }
}

int main(int argc, char *argv[]) {
  std::cout << "------------" << std::endl;
  std::cout << "testDump" << std::endl;
  testDump();
  std::cout << "------------" << std::endl;
  std::cout << "testPack" << std::endl;
  auto obj = testPack();
  std::cout << "------------" << std::endl;
  std::cout << "testUnpack" << std::endl;
  testUnpack(obj);
  std::cout << "------------" << std::endl;
  if (obj)
    obj->decref();
  return 0;
}